﻿// Copyright (c) 2014 Morten Bakkedal
// This code is published under the MIT License.

using System;

using FuncLib.DualFunctions.Collections;
using FuncLib.Functions;

namespace FuncLib.DualFunctions.Compilation
{
	[Serializable]
	public class CompiledDualFunction : Function // or ": SparseFunction"?? not DualFunction!
	{
		private GeneratedFunction generatedFunction; // serializable? or save copy with Variables as entries

		internal CompiledDualFunction(GeneratedFunction generatedFunction)
		{
			this.generatedFunction = generatedFunction;
		}

		protected override double ComputeValue(IEvaluator evaluator)
		{
			// Use PointCache here
			throw new NotImplementedException();
		}

		protected override Function ComputeDerivative(Variable variable)
		{
			if (variable == null)
			{
				throw new ArgumentNullException("variable");
			}

			if (!(variable is DualInnerVariable))
			{
				// A DualFunction can only depend on a DualVariable (or rather its inner DualInnerVariable instance).
				return 0.0;
			}

			return ComputeDerivative(((DualInnerVariable)variable).OuterVariable);
		}

		private Function ComputeDerivative(DualVariable variable)
		{
			if (!DualVariable.CheckFirstOrder(variable))
			{
				throw new DualVariableOrderExceededException(variable);
			}

			if (!generatedFunction.Gradient.Contains(variable))
			{
				return 0.0;
			}

			// Return a wrapper when one of the derivatives of this DualFunction instances is used like an ordinary Function.
			return new DualFunctionFirstDerivative(this, variable);
		}

		public Gradient Gradient(IEvaluator evaluator)
		{
			//generatedFunction.Gradient
			throw new NotImplementedException();
		}

		//public GradientStructure GradientStructure { get; }

		//public Hessian Hessian(IEvaluation evaluation) {}

		//public HessianStructure HessianStructure { get; }

		public string GeneratedCode
		{
			get
			{
				throw new NotImplementedException();
			}
		}

		[Serializable]
		private class DualFunctionFirstDerivative : Function
		{
			private CompiledDualFunction function;
			private DualVariable variable1;

			public DualFunctionFirstDerivative(CompiledDualFunction function, DualVariable variable1)
			{
				this.function = function;
				this.variable1 = variable1;
			}

			protected override double ComputeValue(IEvaluator evaluator)
			{
				return function.Gradient(evaluator)[variable1];
			}
			
			protected override Function ComputeDerivative(Variable variable2)
			{
				if (variable2 == null)
				{
					throw new ArgumentNullException("variable2");
				}

				if (!(variable2 is DualInnerVariable))
				{
					return 0.0;
				}

				return ComputeDerivative(((DualInnerVariable)variable2).OuterVariable);
			}

			private Function ComputeDerivative(DualVariable variable2)
			{
				if (!DualVariable.CheckSecondOrder(variable1, variable2))
				{
					throw new DualVariableOrderExceededException(variable1, variable2);
				}

				//function.HessianStructure.Contains - then order of differentiation doesn't matter!!!!
				if (!function.generatedFunction.Hessian.Contains(variable1, variable2))
				{
					return 0.0;
				}

				//if (variable1.FunctionIndex > variable2.FunctionIndex)
				//{
				//    // Reverse order of differentiation.
				//    return new DualFunctionSecondDerivative(function, variable2, variable1);
				//}

				throw new NotImplementedException();
				//return new DualFunctionSecondDerivative(function, variable1, variable2);
			}
		}
	}
}
